﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DnDLib
{
    public class Packet : ThreadSafeMethods
    {
        const int BITS = 8;
        const int HEADER_BYTES = 7;
        public enum Type { Raw, XML, FilePart };

        Type _type;
        int _timestamp;
        string _content;

        int _length = 0;        
        List<byte> _rawData = new List<byte>();

        #region Properties

        public List<byte> RawData
        {
            get { return _rawData; }
        }

        #endregion

        #region Constructors

        public Packet(List<byte> bytes)
        {
            if (bytes.Count < HEADER_BYTES)
            {
                throw new ArgumentException("List needs at least 5 elements to be a valid packet");
            }

            _length = (bytes[0] << BITS) + bytes[1];
            _timestamp = (bytes[2] << 3 * BITS) + (bytes[3] << 2 * BITS) + (bytes[4] << BITS) + bytes[5];
            _type = (Type)bytes[6];

            if (bytes.Count - HEADER_BYTES < _length)
            {
                throw new ArgumentException(string.Format("The packet is incomplete. Expected {0} bytes, got {1} bytes.", _length, bytes.Count - HEADER_BYTES));
            }

            _rawData.AddRange(bytes.GetRange(0, _length + HEADER_BYTES));
            if ((int) _type < (int) Type.FilePart)
            {
                _content = Encoding.UTF8.GetString(_rawData.GetRange(HEADER_BYTES, _length).ToArray());
            }
        }

        public Packet(List<byte> bytes, bool isFilePart)
        {
            if (isFilePart)
            {
                _length = bytes.Count;
                _type = Type.FilePart;
                SetTimestamp();
                GenerateRawData(bytes);
            }
        }

        public Packet(string str, Type type)
        {
            _type = type;
            _length = str.Length;
            _content = str;
            SetTimestamp();
            GenerateRawData();
        }
        #endregion

        public static List<Packet> ParsePackets(List<byte> bytes)
        {
            bool stop = false;
            int lastIndex = 0;
            List<Packet> packets = new List<Packet>();

            while (!stop)
            {
                try
                {
                    packets.Add(new Packet(bytes.GetRange(lastIndex, bytes.Count - lastIndex)));
                    lastIndex += packets[packets.Count - 1]._rawData.Count;
                }
                catch (ArgumentException e)
                {
                    stop = true;
                }
            }
            return packets;
        }

        public void SetTimestamp()
        {
            _timestamp = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
        }

        public List<byte> GetBytes(int num, int length = 4)
        {
            List<byte> bytes = new List<byte>();
            int andNum;

            for (int i = length; i > 0; i--)
            {
                andNum = (1 << (BITS * i)) - (1 << (BITS * (i - 1)));
                bytes.Add((byte)((num & (andNum)) >> (BITS * (i - 1))));
            }
            return bytes;
        }

        public void GenerateRawData(List<byte> bytes=null)
        {
            _rawData.AddRange(GetBytes(_length, 2));
            _rawData.AddRange(GetBytes(_timestamp, 4));
            _rawData.Add((byte)_type);

            if (bytes == null)
            {
                _rawData.AddRange(Encoding.UTF8.GetBytes(_content));
            }
            else
            {                
                _rawData.AddRange(bytes);
            }
        }

        public override string ToString()
        {
            return string.Format("Length: {0}, Time: {1}, Type: {2}, Contents: {3}", _length, _timestamp, _type, _content);
        }

    }
}
